// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraHUDLayout.h"

#include "CommonUIExtensions.h"
#include "CommonUISettings.h"
#include "GameFramework/InputDeviceSubsystem.h"
#include "GameFramework/InputSettings.h"
#include "GenericPlatform/GenericPlatformInputDeviceMapper.h"
#include "Input/CommonUIInputTypes.h"
#include "ICommonUIModule.h"
#include "LyraLogChannels.h"
#include "NativeGameplayTags.h"
#include "UI/Foundation/LyraControllerDisconnectedScreen.h"
#include "UI/LyraActivatableWidget.h"

#if WITH_EDITOR
#include "CommonUIVisibilitySubsystem.h"
#endif	// WITH_EDITOR

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraHUDLayout)

UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_LAYER_MENU, "UI.Layer.Menu"); // 定义UI层菜单标签
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_ACTION_ESCAPE, "UI.Action.Escape"); // 定义UI动作退出标签
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Platform_Trait_Input_PrimarlyController, "Platform.Trait.Input.PrimarlyController"); // 定义平台特性标签：主要控制器

/**
 * 构造函数
 * @param ObjectInitializer 对象初始化器
 */
ULyraHUDLayout::ULyraHUDLayout(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer) // 调用父类构造函数
	, SpawnedControllerDisconnectScreen(nullptr) // 初始化生成的控制器断开连接屏幕为空
{
	// 默认情况下，只有主要控制器平台需要断开连接屏幕。 
	PlatformRequiresControllerDisconnectScreen.AddTag(TAG_Platform_Trait_Input_PrimarlyController); // 添加主要控制器平台标签
}

/**
 * 初始化时调用
 * 注册UI动作绑定并监听控制器状态变化
 */
void ULyraHUDLayout::NativeOnInitialized()
{
	Super::NativeOnInitialized(); // 调用父类方法

	RegisterUIActionBinding(FBindUIActionArgs(FUIActionTag::ConvertChecked(TAG_UI_ACTION_ESCAPE), false, FSimpleDelegate::CreateUObject(this, &ThisClass::HandleEscapeAction))); // 注册退出动作绑定

	// 如果我们可以显示控制器断开连接屏幕，那么监听控制器状态变化的委托
	if (ShouldPlatformDisplayControllerDisconnectScreen()) // 检查平台是否应该显示控制器断开连接屏幕
	{
		// 绑定到输入设备连接变化
		IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); // 获取平台输入设备映射器
		DeviceMapper.GetOnInputDeviceConnectionChange().AddUObject(this, &ThisClass::HandleInputDeviceConnectionChanged); // 添加输入设备连接变化回调
		DeviceMapper.GetOnInputDevicePairingChange().AddUObject(this, &ThisClass::HandleInputDevicePairingChanged);	// 添加输入设备配对变化回调
	}
}

/**
 * 销毁时调用
 * 移除输入设备连接变化的绑定
 */
void ULyraHUDLayout::NativeDestruct()
{
	Super::NativeDestruct(); // 调用父类方法

	// 移除输入设备连接变化的绑定
	IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); // 获取平台输入设备映射器
	DeviceMapper.GetOnInputDeviceConnectionChange().RemoveAll(this); // 移除所有输入设备连接变化回调
	DeviceMapper.GetOnInputDevicePairingChange().RemoveAll(this); // 移除所有输入设备配对变化回调

	if (RequestProcessControllerStateHandle.IsValid()) // 如果请求处理控制器状态句柄有效
	{
		FTSTicker::GetCoreTicker().RemoveTicker(RequestProcessControllerStateHandle); // 移除定时器
		RequestProcessControllerStateHandle.Reset(); // 重置句柄
	}
}

/**
 * 处理退出动作
 * 推送退出菜单到菜单层
 */
void ULyraHUDLayout::HandleEscapeAction()
{
	if (ensure(!EscapeMenuClass.IsNull())) // 确保退出菜单类不为空
	{
		UCommonUIExtensions::PushStreamedContentToLayer_ForPlayer(GetOwningLocalPlayer(), TAG_UI_LAYER_MENU, EscapeMenuClass); // 推送流式内容到菜单层
	}
}

/**
 * 处理输入设备连接变化
 * 检查是否是当前玩家的设备变化，如果是则通知控制器状态变化
 * @param NewConnectionState 新连接状态
 * @param PlatformUserId 平台用户ID
 * @param InputDeviceId 输入设备ID
 */
void ULyraHUDLayout::HandleInputDeviceConnectionChanged(EInputDeviceConnectionState NewConnectionState, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId)
{
	const FPlatformUserId OwningLocalPlayerId = GetOwningLocalPlayer()->GetPlatformUserId(); // 获取所属本地玩家的平台用户ID

	ensure(OwningLocalPlayerId.IsValid()); // 确保所属本地玩家ID有效

	// 这个设备连接变化发生在不同的玩家身上，对我们来说忽略它。
	if (PlatformUserId != OwningLocalPlayerId) // 如果平台用户ID不匹配
	{
		return;
	}

	NotifyControllerStateChangeForDisconnectScreen(); // 通知控制器状态变化以处理断开连接屏幕
}

/**
 * 处理输入设备配对变化
 * 检查是否是当前玩家的设备配对变化，如果是则通知控制器状态变化
 * @param InputDeviceId 输入设备ID
 * @param NewUserPlatformId 新用户平台ID
 * @param OldUserPlatformId 旧用户平台ID
 */
void ULyraHUDLayout::HandleInputDevicePairingChanged(FInputDeviceId InputDeviceId, FPlatformUserId NewUserPlatformId, FPlatformUserId OldUserPlatformId)
{
	const FPlatformUserId OwningLocalPlayerId = GetOwningLocalPlayer()->GetPlatformUserId(); // 获取所属本地玩家的平台用户ID

	ensure(OwningLocalPlayerId.IsValid()); // 确保所属本地玩家ID有效

	// 如果这个配对变化与我们的本地玩家相关，则通知变化。
	if (NewUserPlatformId == OwningLocalPlayerId || OldUserPlatformId == OwningLocalPlayerId) // 如果新旧用户平台ID有一个匹配当前玩家
	{
		NotifyControllerStateChangeForDisconnectScreen();	// 通知控制器状态变化以处理断开连接屏幕
	}
}

/**
 * 检查平台是否应该显示控制器断开连接屏幕
 * @return 如果平台应该显示控制器断开连接屏幕则返回true
 */
bool ULyraHUDLayout::ShouldPlatformDisplayControllerDisconnectScreen() const
{
	// 我们只希望在主要控制器平台上显示此菜单
	bool bHasAllRequiredTags = ICommonUIModule::GetSettings().GetPlatformTraits().HasAll(PlatformRequiresControllerDisconnectScreen); // 检查平台特性是否包含所有所需标签

	// 检查我们可能在编辑器中模拟的标签
#if WITH_EDITOR
	const FGameplayTagContainer& PlatformEmulationTags = UCommonUIVisibilitySubsystem::Get(GetOwningLocalPlayer())->GetVisibilityTags(); // 获取平台模拟标签
	bHasAllRequiredTags |= PlatformEmulationTags.HasAll(PlatformRequiresControllerDisconnectScreen); // 使用或运算检查平台模拟标签
#endif	// WITH_EDITOR

	return bHasAllRequiredTags; // 返回是否具有所有所需标签
}

/**
 * 通知控制器状态变化以处理断开连接屏幕
 * 在下一次tick时处理控制器设备状态
 */
void ULyraHUDLayout::NotifyControllerStateChangeForDisconnectScreen()
{
	// 我们只应该在绑定到控制器状态变化委托时到达这里
	ensure(ShouldPlatformDisplayControllerDisconnectScreen()); // 确保平台应该显示控制器断开连接屏幕

	// 如果我们还没有，在下一次tick时排队处理设备状态。
	if (!RequestProcessControllerStateHandle.IsValid()) // 如果请求处理控制器状态句柄无效
	{
		RequestProcessControllerStateHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this, [this](float DeltaTime) // 添加定时器委托
		{
			RequestProcessControllerStateHandle.Reset(); // 重置句柄
			ProcessControllerDevicesHavingChangedForDisconnectScreen(); // 处理控制器设备变化以处理断开连接屏幕
			return false; // 返回false表示只执行一次
		}));
	}
}

/**
 * 处理控制器设备变化以处理断开连接屏幕
 * 检查玩家是否有任何连接的控制器，如果没有则显示断开连接菜单
 */
void ULyraHUDLayout::ProcessControllerDevicesHavingChangedForDisconnectScreen()
{
	// 我们只应该在绑定到控制器状态变化委托时到达这里
	ensure(ShouldPlatformDisplayControllerDisconnectScreen()); // 确保平台应该显示控制器断开连接屏幕
	
	const FPlatformUserId OwningLocalPlayerId = GetOwningLocalPlayer()->GetPlatformUserId(); // 获取所属本地玩家的平台用户ID
	
	ensure(OwningLocalPlayerId.IsValid()); // 确保所属本地玩家ID有效

	// 获取映射到我们玩家的所有输入设备
	const IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); // 获取平台输入设备映射器
    TArray<FInputDeviceId> MappedInputDevices; // 映射的输入设备数组
    const int32 NumDevicesMappedToUser = DeviceMapper.GetAllInputDevicesForUser(OwningLocalPlayerId, OUT MappedInputDevices); // 获取用户的所有输入设备

    // 检查是否有任何其他连接的游戏手柄设备映射到此平台用户。 
    bool bHasConnectedController = false; // 是否有连接的控制器

    for (const FInputDeviceId MappedDevice : MappedInputDevices) // 遍历所有映射的输入设备
    {
    	if (DeviceMapper.GetInputDeviceConnectionState(MappedDevice) == EInputDeviceConnectionState::Connected) // 如果设备连接状态为已连接
    	{
    		const FHardwareDeviceIdentifier HardwareInfo = UInputDeviceSubsystem::Get()->GetInputDeviceHardwareIdentifier(MappedDevice); // 获取硬件设备标识符
    		if (HardwareInfo.PrimaryDeviceType == EHardwareDevicePrimaryType::Gamepad) // 如果主要设备类型为游戏手柄
    		{
    			bHasConnectedController = true; // 设置存在连接的控制器
    		}
    	}			
    }

    // 如果没有游戏手柄输入设备映射到此用户，那么我们希望弹出提示要求重新连接它们
    if (!bHasConnectedController) // 如果没有连接的控制器
    {
    	DisplayControllerDisconnectedMenu(); // 显示控制器断开连接菜单
    }
	// 否则，如果当前正在显示屏幕，我们可以隐藏它
	else if (SpawnedControllerDisconnectScreen) // 如果存在生成的控制器断开连接屏幕
	{
		HideControllerDisconnectedMenu(); // 隐藏控制器断开连接菜单
	}
}

/**
 * 显示控制器断开连接菜单的实现
 * 推送控制器断开连接控件到菜单层
 */
void ULyraHUDLayout::DisplayControllerDisconnectedMenu_Implementation()
{
	UE_LOG(LogLyra, Log, TEXT("[%hs] Display controller disconnected menu!"), __func__); // 记录日志

	if (ControllerDisconnectedScreen) // 如果控制器断开连接屏幕类有效
	{
		// 推送"控制器断开连接"控件到菜单层
		SpawnedControllerDisconnectScreen = UCommonUIExtensions::PushContentToLayer_ForPlayer(GetOwningLocalPlayer(), TAG_UI_LAYER_MENU, ControllerDisconnectedScreen); // 推送内容到菜单层
	}
}

/**
 * 隐藏控制器断开连接菜单的实现
 * 从菜单层弹出控制器断开连接控件
 */
void ULyraHUDLayout::HideControllerDisconnectedMenu_Implementation()
{
	UE_LOG(LogLyra, Log, TEXT("[%hs] Hide controller disconnected menu!"), __func__); // 记录日志
	
	UCommonUIExtensions::PopContentFromLayer(SpawnedControllerDisconnectScreen); // 从层中弹出内容
	SpawnedControllerDisconnectScreen = nullptr; // 设置生成的控制器断开连接屏幕为空
}